﻿package net.ssta.core.config;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * @since JDK 1.5
 * @author HillEric
 *
 */
abstract public class Config
{
	private static Logger logger = Logger.getLogger(Config.class);
	private static Map<String,Config> configsMap = null;
	private static Map<String,RefreshMode> m_refreshModeMap = new HashMap<String,RefreshMode>();
	private static String m_sConfigFileName = "common_component.xml";

	/**
	 * 根据配置文件的类型和配置文件的路径获取一个配置文件实例
	 * @param type 文件类型：properties和xml
	 * @param name 配置文件的标识
	 * @param file 配置文件的路径，如果以'/'或者盘符开头，则表明是绝对路径，否则就是相对于CLASSPATH的路径
	 * @return
	 * @throws IOException
	 * @throws URISyntaxException
	 */
	public static Config addConfig(ConfigType type,String name, String file) throws ConfigException
	{
		if(configsMap == null) loadConfig();
		Config config = null;
		switch(type)
		{
			case PROP: config = new PropertiesConfig( file ); break;
			case XML: break;
		}
		configsMap.put(name, config);
		return config ;
	}
	
	public static Config getConfig(String name)
	{
		if(configsMap == null) loadConfig();
		Config config = configsMap.get(name);
		if(config == null) throw new ConfigException("no config named " + name + " was found.");
		return config;
	}
	
	public static Config removeConfig(String name)
	{
		if(configsMap == null) return null;
		Config config = configsMap.get(name);
		if(config == null) return null;
		configsMap.remove(name);
		return config;
	}
	
	private static void loadConfig()
	{
		synchronized (m_sConfigFileName) 
		{
			if(configsMap == null)
			{
				Map<String,Config> map = new ConcurrentHashMap<String,Config>();
				InputStream is = null;
				try 
				{
					//使用w3c的dom解析xml
					DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 
					factory.setValidating(false); 
					DocumentBuilder builder = factory.newDocumentBuilder();
					ClassLoader ccl = Thread.currentThread().getContextClassLoader();
					is = ccl.getResourceAsStream(m_sConfigFileName);
					if(is == null)
					{
						logger.warn("can not find datasource config file " + m_sConfigFileName + ",maybe it is missing,please make sure it exists in classpath.");
						configsMap = map;
						return ;
					}
					Document document = builder.parse(is); 
					Element root = document.getDocumentElement(); 
					if(root == null)
					{
						logger.warn("the config file " + m_sConfigFileName + " has no root element,maybe it is damaged.");
						configsMap = map;
						return ;
					}
					//使用xpath查找数据源配置的节点，并将该元素保存到m_configElement中
					XPathFactory xpf = XPathFactory.newInstance();
		            XPath xpath = xpf.newXPath();
		            Node configElement = (Node) xpath.evaluate("ConfigProperties", root,XPathConstants.NODE);
					if(configElement == null)
					{
						logger.warn("the config file " + m_sConfigFileName + " has none element about properties.");
						configsMap = map;
						return ;
					}
					
		            NodeList configNodes = (NodeList) xpath.evaluate("Properties", configElement,XPathConstants.NODESET);
		            if(configNodes == null) return ;
		            Map<String,String> properties = new HashMap<String,String>();
		            for(int i=0;i<configNodes.getLength();i++)
		            {
		            	properties.clear();
		            	Node configNode = configNodes.item(i);			             
			            NodeList propNodes = configNode.getChildNodes();
			            for(int j=0;j<propNodes.getLength();j++)
			            {
			            	Node propNode = propNodes.item(j);
			     	      	if(Node.ELEMENT_NODE != propNode.getNodeType()) continue;
			     	      	Node text = propNode.getFirstChild();
			     	      	properties.put(propNode.getNodeName(), text == null ? "":text.getNodeValue());
			            }
			            if("false".equalsIgnoreCase(properties.get("enable"))) continue;
			            if("proerties".equalsIgnoreCase(properties.get("type")))
			            {
			            	Config config = new PropertiesConfig( properties.get("url") );
			            	map.put(properties.get("name"), config);
			            }
					}
		            configsMap = map;					
				}
				catch(Exception e)
				{
					logger.error("something is wrong when load the config,error message is " + e.getMessage());
					configsMap = map;
				}
				finally
				{
					try
					{
						if(is != null)	is.close();
					} 
					catch (Exception e)
					{
						//do nothing
					}
				}
			}
		}
	}
		
	static
	{
		m_refreshModeMap.put("AUTO", RefreshMode.AUTO);
		m_refreshModeMap.put("MANUAL", RefreshMode.MANUAL);
		m_refreshModeMap.put("INTERVAL", RefreshMode.INTERVAL);	
	}
	
	private long m_lastModified = 0;
	private File m_file = null;
	private RefreshMode m_refreshMode;
	
	protected Config(String sfile)
	{
		try 
		{
			if(sfile == null || "".equals(sfile)) throw new IllegalArgumentException("property file name can not be null or empty.");
			if(sfile.startsWith("/") || sfile.startsWith("\\") || sfile.indexOf(":") > 0)
			{
				m_file = new File(sfile);
				if(!m_file.exists()) throw new ConfigException(new FileNotFoundException(m_file.getAbsolutePath()));
			}
			else
			{
				//在CLASSPATH下查找file指定的配置文件，生成文件对象，然后加载配置文件   
				ClassLoader ccl = Thread.currentThread().getContextClassLoader();
				URL url = ccl.getResource(sfile);
				if(url == null)
				{
					throw new ConfigException(new FileNotFoundException(ccl.getResource("").toString()+sfile));
				}
				m_file = new File(url.toURI());
			}
		}
		catch (URISyntaxException e)
		{
			throw new ConfigException(e);
		}
		load();
	}
	
	/**
	 * 从文件加载配置文件到内存中，成功加载返回TRUE，否则返回FALSE
	 * @return
	 * @throws Exception
	 */
	abstract protected boolean load(File configFile) throws ConfigException;
	
	/**
	 * 取得以key标识的配置对应的值，如果指定标识对应的配置不存在，则返回NULL
	 * @param key
	 * @return
	 */
	abstract protected String getValue(String key);
	
	protected boolean load() throws ConfigException
	{
		boolean bRet = false;
		if(!m_file.exists()) //判断文件是否存在
		{
			throw new ConfigException(new FileNotFoundException(m_file.getAbsolutePath()));
		}
		bRet = load(m_file);
		m_lastModified = m_file.lastModified();//更新文件的最后修改时间
		m_refreshMode = (RefreshMode)m_refreshModeMap.get(getProperty("REFRESH_MODE","MANUAL"));
		bRet = true;
		return bRet;
	}
	/**
	 * 取得以key标识的配置对应的值，如果指定标识对应的配置不存在，则返回默认值
	 * @param key
	 * @param defaultValue
	 * @return
	 */
	public final String getProperty(String key, String defaultValue)
	{
		if(RefreshMode.AUTO.equals(m_refreshMode))
		{
			try
			{
				//配置文件在最近一次加载后被修改过，则重新加载
				if(m_file.lastModified() > m_lastModified)
				{
					load();
				}				
			}
			catch(Exception e)
			{
				e.printStackTrace();
			}
		}
		String value = getValue(key);
		return value == null ? defaultValue : value;
	}
	
	/**
	 * 获取配置文件中包含的所有键，返回值为一个数组
	 * @return
	 */
	abstract public String[] getPropertyNames();

	/**
	 * 取得与配置文件对应的文件对象
	 * @return
	 */
	protected File getConfigFile()
	{
		return m_file;
	}
}

/**
 * 配置文件的更新类型
 * @author HillEric
 *
 */
enum RefreshMode
{
	/**
	 * 自动更新，即文件变化时更新
	 */
	AUTO,
	/**
	 * 手动更新，重新启动程序后更新
	 */
	MANUAL,
	/**
	 * 定时更新，间隔一定时间更新
	 */
	INTERVAL
}
